﻿namespace AntBot
{
	public class HashTable
	{
		//size 64 + 8 + (32 + 32) + 32 + 32 = 200
		public struct HashEntry
		{
			public ulong HashCode;
			public byte Depth;
			public int Value;
			public int BestMoveFrom;
			public int BestMoveTo;
		}

		private readonly uint mHashTableSize;
		readonly HashEntry[] mTable;
		public uint SlotsUsed { get; private set; }

		public unsafe HashTable()
		{
			mHashTableSize = (uint)((Config.HashTableSizeMb * 1024 * 8) / sizeof(HashEntry));
			mTable = new HashEntry[mHashTableSize];
			Clear();
		}

		public void Clear()
		{
			SlotsUsed = 0;
			ResetStats();
			for (uint intIndex = 0; intIndex < mHashTableSize; intIndex++)
			{
				mTable[intIndex].HashCode = 0;
				mTable[intIndex].Depth = byte.MinValue;
				mTable[intIndex].BestMoveFrom = -1;
				mTable[intIndex].BestMoveTo = -1;
				mTable[intIndex].Value = int.MaxValue;
			}
		}

		public unsafe bool ProbeHash(ulong hashCode, byte depth, out HashEntry entry)
		{
			Probes++;

			fixed (HashEntry* hashBase = &mTable[0])
			{
				HashEntry* hashEntry = hashBase;
				hashEntry += ((uint)(hashCode % mHashTableSize));

				entry = *hashEntry;

				if (hashEntry->HashCode == hashCode)
				{
					Hits++;
					if (hashEntry->Depth >= depth) FullHits++;
					return true;
				}
			}

			return false;
		}

		public unsafe void RecordHash(ulong hashCode, byte depth, int value, int bestMoveFrom, int bestMoveTo)
		{
			Writes++;

			fixed (HashEntry* hashBase = &mTable[0])
			{
				HashEntry* hashEntry = hashBase;
				hashEntry += ((uint)(hashCode % mHashTableSize));

				if (hashEntry->HashCode != 0)
				{
					if (hashEntry->Depth > depth)
						return;

					Overwrites++;
					if (hashEntry->HashCode != hashCode)
						Collisions++;
				}
				else
					SlotsUsed++;

				hashEntry->HashCode = hashCode;
				hashEntry->BestMoveFrom = bestMoveFrom;
				hashEntry->BestMoveTo = bestMoveTo;
				hashEntry->Depth = depth;
				hashEntry->Value = value;
			}
		}

		public override string ToString()
		{
			return
				"Size: " + mHashTableSize +
				", SlotsUsed: " + SlotsUsed +
				", Probes: " + Probes +
				", Hits: " + Hits +
				", FullHits: " + FullHits +
				", Writes: " + Writes +
				", Collisions: " + Collisions +
				", Overwrites: " + Overwrites;
		}

		#region Statistics

		public int Probes { get; private set; }
		public int Hits { get; private set; }
		public int FullHits { get; private set; }
		public int Writes { get; private set; }
		public int Collisions { get; private set; }
		public int Overwrites { get; private set; }

		public void ResetStats()
		{
			Probes = 0;
			Hits = 0;
			FullHits = 0;
			Writes = 0;
			Collisions = 0;
			Overwrites = 0;
		}

		#endregion
	}
}
